using System;
using System.Collections.Generic;
using BEPUphysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SynapseGaming.LightingSystem.Collision;
using SynapseGaming.LightingSystem.Core;
using SynapseGaming.LightingSystem.Rendering;

namespace Indiefreaks.Xna.Physics
{
    /// <summary>
    /// Manager wrapping BEPUPhysics features
    /// </summary>
    public class BEPUPhysicsManager : ICollisionManager
    {
        private readonly List<SceneEntity> _sceneEntities = new List<SceneEntity>();
        private readonly List<BEPUCollisionMove> _cachedCollisionMoves = new List<BEPUCollisionMove>();
        private readonly List<BEPUCollisionMove> _frameCollisionMoves = new List<BEPUCollisionMove>();

        /// <summary>
        /// Creates a new instance
        /// </summary>
        /// <param name="graphicsDeviceService"/>
        public BEPUPhysicsManager(IGraphicsDeviceService graphicsDeviceService)
        {
            ManagerProcessOrder = 10;
            Space = new Space();
            GraphicsDeviceManager = graphicsDeviceService;
        }

        /// <summary>
        /// Returns the current BEPUPhysics Space instance
        /// </summary>
        public Space Space { get; private set; }

        #region Implementation of IUnloadable

        /// <summary>
        /// Disposes any graphics resource used internally by this object, and removes
        ///             scene resources managed by this object. Commonly used during Game.UnloadContent.
        /// </summary>
        public void Unload()
        {
            Clear();
        }

        #endregion

        #region Implementation of IManager

        /// <summary>
        /// Use to apply user quality and performance preferences to the resources managed by this object.
        /// </summary>
        /// <param name="preferences"/>
        public void ApplyPreferences(ILightingSystemPreferences preferences)
        {
        }

        /// <summary>
        /// Removes resources managed by this object. Commonly used while clearing the scene.
        /// </summary>
        public void Clear()
        {
            for (int i = 0; i < Space.Entities.Count; i++)
                Space.Remove(Space.Entities[i]);
        }

        /// <summary>
        /// Updates the object and its contained resources.
        /// </summary>
        /// <param name="gameTime"/>
        public void Update(GameTime gameTime)
        {
            _sceneEntities.Clear();
            _frameCollisionMoves.Clear();

            SceneInterface.ActiveSceneInterface.ObjectManager.FindFast(_sceneEntities);

            foreach (var sceneEntity in _sceneEntities)
            {
                if (sceneEntity is ICollisionObject && ((ICollisionObject)sceneEntity).CollisionMove is BEPUCollisionMove)
                {
                    var collisionMove = ((ICollisionObject) sceneEntity).CollisionMove as BEPUCollisionMove;
                    _frameCollisionMoves.Add(collisionMove);
                }
            }

            foreach (var cachedCollisionMove in _cachedCollisionMoves)
            {
                if (!_frameCollisionMoves.Contains(cachedCollisionMove) && cachedCollisionMove.IsInitialized)
                    Space.Remove(cachedCollisionMove.SpaceObject);
            }

            _cachedCollisionMoves.Clear();
            _cachedCollisionMoves.AddRange(_frameCollisionMoves);

            foreach (var bepuCollisionMove in _frameCollisionMoves)
            {
                if (!bepuCollisionMove.IsInitialized)
                {
                    bepuCollisionMove.Initialize();
                    Space.Add(bepuCollisionMove.SpaceObject);
                }

                bepuCollisionMove.Begin();
            }

            if (SceneInterface.ActiveSceneInterface.Editor != null && SceneInterface.ActiveSceneInterface.Editor.EditorAttached)
                return;

            Space.Update((float) gameTime.ElapsedGameTime.TotalMilliseconds);

            foreach (var bepuCollisionMove in _frameCollisionMoves)
            {
                bepuCollisionMove.End();
            }
        }

        #endregion

        #region Implementation of IManagerService

        /// <summary>
        /// Gets the manager specific Type used as a unique key for storing and
        ///             requesting the manager from the IManagerServiceProvider.
        /// </summary>
        public Type ManagerType
        {
            get { return typeof (BEPUPhysicsManager); }
        }

        /// <summary>
        /// Sets the order this manager is processed relative to other managers
        ///             in the IManagerServiceProvider. Managers with lower processing order
        ///             values are processed first.
        ///             In the case of BeginFrameRendering and EndFrameRendering, BeginFrameRendering
        ///             is processed in the normal order (lowest order value to highest), however
        ///             EndFrameRendering is processed in reverse order (highest to lowest) to ensure
        ///             the first manager begun is the last one ended (FILO).
        /// </summary>
        public int ManagerProcessOrder { get; set; }

        #endregion

        #region Implementation of IRenderableManager

        /// <summary>
        /// Sets up the object prior to rendering.
        /// </summary>
        /// <param name="scenestate"/>
        public void BeginFrameRendering(ISceneState scenestate)
        {
            if (Space.ForceUpdater.Gravity.Y != -scenestate.Environment.Gravity)
                Space.ForceUpdater.Gravity = new Vector3(0, -scenestate.Environment.Gravity, 0);
        }

        /// <summary>
        /// Finalizes rendering.
        /// </summary>
        public void EndFrameRendering()
        {
        }

        /// <summary>
        /// The current GraphicsDeviceManager used by this object.
        /// </summary>
        public IGraphicsDeviceService GraphicsDeviceManager { get; private set; }

        #endregion
    }
}